/*
BioShape - 3D shape-based modelling and simulation environment
Copyright (C) 2010-2012 UNICAM - University of Camerino - School of Science and 
Technology - Computer Science Division

-Developers-
Group leaders: Flavio Corradini, Emanuela Merelli and Luca Tesei 
Federico Buti, Gaston Alanis, Matteo Micheletti, Andrea Piermarteri, Carmine 
Oliva.

This file is part of BioShape.

BioShape is free software: you can redistribute it and/or modify it under the 
terms of the GNU Lesser General Public License as published by the Free Software
Foundation, either version 3 of the License, or (at your option) any later 
version.

This program is distributed in the hope that it will be useful, but WITHOUT ANY 
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 
PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License along 
with this program. If not, see <http://www.gnu.org/licenses/>.

Emanuela Merelli
School of Science and Technology
University of Camerino
Via Madonna delle Carceri, 62032 Camerino, Italy
e.@mail: emanuela.merelli@unicam.it
 */
package it.unicam.cs.cosy.bioshape.core;

import hermesV3.BasicServices;
import hermesV3.util.PropertiesLoader;
import hermesV3.util.Utils;
import hermesV3.PlaceDescriptor;
import hermesV3.agent.Agent;
import hermesV3.agent.ServiceAgent;
import hermesV3.basicServices.communication.CommunicationManager;
import hermesV3.basicServices.discovery.DiscoveryManager;
import hermesV3.basicServices.genesis.GenesisManager;
import hermesV3.basicServices.mobility.MobilityManager;
import hermesV3.core.id.IdManager;
import hermesV3.core.net.SendReceive;
import hermesV3.core.starter.AgentStarter;
import hermesV3.util.HermesLogger;
import hermesV3.util.loggers.HermesGUILogger;
import hermesV3.util.loggers.LoggingTable;
import it.unicam.cs.cosy.bioshape.gui.Monitor;

import java.awt.Dimension;
import java.io.File;
import java.io.FileNotFoundException;
import java.lang.reflect.Constructor;
import java.util.Properties;

/**
 * It is the main class of the platform. It represents the server to accept and
 * to launch new agents, to receive and to send messages. It makes new instance
 * of the different components of the platform and links every component to the
 * others.
 * 
 * @author Federico Buti
 */
public class SimulationEntry {
	/** The home directory of the platform */
	private final static String hermesHome = "bin/hermesV3";
	// TODO Avoid path strings, use only configuration files
	
	/** Name of the properties file (by default "hermes.properties") */
	private String filePropertiesName = "/conf/hermes.properties";
    // TODO Avoid path strings, use only configuration files
	
	/** System properties */
	private Properties properties = null;

	/** The current PlaceDescriptor. */
	private static PlaceDescriptor currentPlaceDescriptor;

	/** Represents the <code>SendReceive</code> component */
	private SendReceive sendReceive;

	/** Represents the <code>AgentStarter</code> component */
	private AgentStarter agentStarter;

	/**
	 * Represents the <code>SecurityManager</code> component
	 */
	// private SecurityManager securityManager;

	/**
	 * Manages the agent {@code Identifier}s and handles the position of
	 * agents which born in the current place.
	 */
	private IdManager idManager;

	/**
	 * The reference of the Mobility component. Offers the mobility service to
	 * the agent layer.
	 */
	private MobilityManager mobilityManager;

	/**
	 * The reference of the Discovery component. Offers a way to know every
	 * service agents registered into the place.
	 */
	private DiscoveryManager discoveryManager;

	/**
	 * The reference of the Genesis component. It could starts new agents, clone
	 * new agents and it handles the agent death.
	 */
	private GenesisManager genesisManager;

	/**
	 * The reference of the Communication component (runnable). Offers to the
	 * Agent layer a way to communicate.
	 */
	private CommunicationManager communicationManager;

	/** The thread associated with the Communication component. */
	private Thread communicationThread;

	/** The thread associated with the Discovery component. */
	private Thread discoveryThread;

	private LoggingTable lt;

	private Monitor hermesGUI;

	/**
	 * Creates a new instance of {@code Place} with all the support agents for
	 * the simulation.
	 */
	public SimulationEntry() {
	    // Load Hermes and (only 2) BioShape properties from the unique file 
	    // hermes.properties
	    // TODO Avoid to mix BioShape and Hermes properties in the same property
	    // file
		this.properties = loadProperties();
		if (this.properties == null) {
		    // Something went wrong with the properties
			System.exit(1);
		}
		// Initialize the logger
		loadLogger(this.properties);
		// Log initial message
		HermesLogger.log(HermesLogger.INFO, "BioShape 0.9, Copyright "
				+ "(C) 2010-2112 Flavio Corradini, Emanuela Merelli and Luca Tesei;");
		HermesLogger.log(HermesLogger.INFO, "Developers: Luca Tesei, Federico Buti, "
				+ "Gaston Alanis, Matteo Micheletti, Andrea Piermarteri, "
				+ "Carmine Oliva.");
		HermesLogger.log(HermesLogger.INFO, "Hermes porting to Java 6: "
				+ "Federico Buti");
		HermesLogger.log(HermesLogger.INFO, "GNU Lesser Public License");
		HermesLogger.log(HermesLogger.INFO, "BioShape comes with "
				+ "ABSOLUTELY NO WARRANTY;");
		HermesLogger.log(HermesLogger.INFO, "This program " + "is "
				+ "distributed in the hope that it will be useful, \nbut "
				+ "WITHOUT ANY WARRANTY; without even the implied warranty \n"
				+ "of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. \n"
				+ "See the GNU Lesser General Public License for more "
				+ "details.");
		HermesLogger.log(HermesLogger.DEBUG, "Properties loaded" + ": "
				+ properties);
		// Initialize Hermes Network
		sendReceive = loadNet(properties);
		currentPlaceDescriptor = sendReceive.getCurrentPlaceDescriptor();
		// Initialize Hermes basic services
		agentStarter = new AgentStarter(
				currentPlaceDescriptor.getPlaceAddress(), properties);
		idManager = new IdManager(currentPlaceDescriptor.getPlaceAddress());
		mobilityManager = new MobilityManager(
				currentPlaceDescriptor.getPlaceAddress());
		communicationManager = new CommunicationManager(
				currentPlaceDescriptor.getPlaceAddress());
		discoveryManager = new DiscoveryManager();
		genesisManager = new GenesisManager(
				currentPlaceDescriptor.getPlaceAddress());
		// core level association
		sendReceive.setStarter(agentStarter);
		sendReceive.setTrace(idManager);
		agentStarter.setIdentify(idManager);
		agentStarter.setTrace(idManager);
		// BasicServices level association
		mobilityManager.setEnvelopeIO(sendReceive);
		mobilityManager.setIdentify(idManager);
		mobilityManager.setTrace(idManager);
		discoveryManager.setIdentify(idManager);
		discoveryManager.setPlaceDescriptorIO(sendReceive);
		discoveryThread = new Thread(discoveryManager);
		discoveryThread.start();
		genesisManager.setStarter(agentStarter);
		genesisManager.setIdentify(idManager);
		genesisManager.setTrace(idManager);
		genesisManager.setEnvelopeIO(sendReceive);
		communicationManager.setEnvelopeIO(sendReceive);
		communicationManager.setTrace(idManager);
		communicationThread = new Thread(communicationManager);
		communicationThread.start();
		// without security manager...not useful
		BasicServices basicServices = new BasicServices(mobilityManager,
				communicationManager, communicationManager, discoveryManager,
				discoveryManager, discoveryManager, genesisManager, null);
		agentStarter.setBasicServices(basicServices);
		// always load service agents!
		HermesLogger.log(HermesLogger.DEBUG, "Starting to load Service Agents");
		loadServiceAgents();
		HermesLogger.log(HermesLogger.DEBUG, "Service Agents loaded");
		// always show GUI
		hermesGUI = new Monitor(currentPlaceDescriptor.getPlaceAddress(),
				hermesHome, basicServices, idManager, idManager, idManager);
		hermesGUI.setPreferredSize(new Dimension(950, 800));
		hermesGUI.pack();
		hermesGUI.setLocationRelativeTo(null);
		hermesGUI.createLogTable(lt);
		hermesGUI.setVisible(true);
		HermesLogger.log(HermesLogger.INFO, "Place ready!");
	}

	/**
	 * 
	 * Launches new agent directly from the Place instance using the Starter
	 * component.
	 * 
	 * @param agent
	 *            The agent to start.
	 */
	public void launchUserAgent(Agent agent) {
		if (agentStarter == null) {
			HermesLogger.log(HermesLogger.WARNING,
					"No Starter component found!");
		} else {
			agentStarter.startAgent(agent, Agent.INIT, false);
		}
	}

	/**
	 * This methods is use to launch into the place a service agent. It is used
	 * when the place starts.
	 * 
	 * @param agent
	 *            The service agent to start.
	 */
	private void launchServiceAgent(Agent agent) {
		if (agentStarter == null) {
			HermesLogger.log(HermesLogger.WARNING,
					"No Starter component found!");
		} else {
			agentStarter.startAgent(agent, Agent.INIT, true);
		}
	}

	/**
	 * It loads every service agents stored into the HERMES.SERVICES directory.
	 * It uses the launchServiceAgent method.
	 */
	private void loadServiceAgents() {
		Class<?> clsAgent = null;
		String className = null;
		String fileName = null;
		ServiceAgent serviceAgent = null;
		try {
			String saPath = properties.getProperty("HERMES.SERVICES_DIRECTORY");
			File dir = new File(saPath);
			File[] files = dir.listFiles();
			int numFiles = files.length;
			//
			for (int i = 0; i < numFiles; i++) {
				fileName = files[i].getName();
				if (fileName.endsWith(".class")) {
					// Get the service agent's name (file name without .class)
					className = fileName.substring(0,
							fileName.indexOf(".class"));
					// Load the service agent class from the service directory
					clsAgent = Class.forName("hermesV3.services." + className);
					if (clsAgent.getSuperclass().getName()
							.endsWith("hermesV3.agent.ServiceAgent")) {
						serviceAgent = (ServiceAgent) (clsAgent
								.getConstructor(new Class[] { String.class })
								.newInstance(new Object[] { className }));
						launchServiceAgent(serviceAgent);
					}
				}
			}
		} catch (Exception e) {
			HermesLogger.log(HermesLogger.WARNING,
					e.getClass().getSimpleName(), e);
		}
	}

	/**
	 * Main entry point of the application. Starts the services of Hermes
	 * platform, including the GUI and all the support agents for the
	 * simulation.
	 * 
	 * @param args
	 *            command line arguments (NONE)
	 */
	@SuppressWarnings("unused")
	public static void main(String[] args) {
		new SimulationEntry();
	}

	/**
	 * Load properties from a file
	 * 
	 * @return properties loaded.
	 */
	private Properties loadProperties() {
		Properties properties = null;
		try {
			properties = PropertiesLoader.loadProperties(hermesHome,
					filePropertiesName);
		} catch (FileNotFoundException fnfe) {
			System.out.println("The properties file: " + hermesHome
					+ "/conf/" + filePropertiesName + "was not found.\n"
					+ fnfe);
		} catch (Exception e) {
			System.out.println("Something wrong in the properties file: "
					+ hermesHome + "/conf/" + filePropertiesName + "\n" + e);
			properties = null;
		}
		return properties;
	}

    /**
     * Sets the right logger from the {@code Properties}. If the logger is the
     * GUI logger it also sets the {@code LoggingTable} reference passed to the
     * GUI constructor.
     * 
     * @param properties
     *            The {@code Properties} of the {@code Place}.
     */
    private void loadLogger(Properties properties) {
        HermesLogger.setLevel(0);
        if (!properties.getProperty("HERMES.LOG").equals("true"))
            return;// no log facility required by user, return...
        // ...else
        HermesLogger.reset();
        String loggerLevel = properties.getProperty("HERMES.LOG_LEVEL");
        HermesLogger.setLevel(loggerLevel);
        try {
            Class<?> loggerClass = Class.forName(properties
                    .getProperty("HERMES.LOG_CLASS"));
            loggerClass.getConstructor(
                    new Class[] { java.util.Properties.class }).newInstance(
                    new Object[] { properties });
        } catch (Exception e) {
            e.printStackTrace();
            Utils.guiError("Error while loading the logger", "Logger error", e);
            System.exit(1);
        }
        // if the logger is the GUI logger we set the GUI logging table
        if (HermesLogger.driver instanceof HermesGUILogger)
            lt = ((HermesGUILogger) HermesLogger.driver).getTable();
    }

    /**
     * Loads the right net component from the {@code Properties}.
     * 
     * @param properties
     *            The {@code Properties} of the {@code Place}.
     * @return The network component.
     */
    private SendReceive loadNet(Properties properties) {
        SendReceive sendReceiver = null;
        try {
            Class<?> sendReceiveClass = Class.forName(properties
                    .getProperty("HERMES.NET_CLASS"));
            Constructor<?> c = sendReceiveClass.getConstructor(new Class[] {
                    java.util.Properties.class, java.lang.String.class });
            sendReceiver = (SendReceive) c
                    .newInstance(new Object[] { properties,
                            properties.getProperty("HERMES.NET_PROTOCOL") });
        } catch (Exception e) {
            HermesLogger.log(HermesLogger.WARNING, e.getMessage(),
                    e.getCause() == null ? e : (Exception) e.getCause());
            Utils.guiError(e.getCause() == null ? e.getMessage() : e.getCause()
                    .getMessage(), "Error while creating the Server "
                    + "socket", e.getCause() == null ? e : e.getCause());
            System.exit(1);
        }
        return sendReceiver;
    }
}